package at.fhj.antesk.steamvaluecalc;

import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JProgressBar;
import javax.swing.JTable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import at.fhj.antesk.steamvaluecalc.interfaces.Retriever;
import at.fhj.antesk.steamvaluecalc.resources.Configs;
import at.fhj.antesk.steamvaluecalc.utils.HelperUtil;

public class GameDataRetriever implements Retriever {

	private Map<String, String[]> csvMap = Configs.getInstance().getCsvMap();
	private Map<String, String> configsMap = Configs.getInstance().getConfigsMap();
	private Map<Integer, Double> gamePricesFromDatabse;

	private DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

	private static Logger logger = LoggerFactory.getLogger(GameDataRetriever.class);

	private String currency = "\u20AC";

	private java.sql.Connection connection = new DatabaseAccess().getConnection();
	private boolean databaseEmpty;
	private MainFrame mainFrame;

	public void setMainFrame(MainFrame mainFrame) {
		this.mainFrame = mainFrame;
	}

	@Override
	public double retrievePriceFromUrl(String url) throws IOException {
		Connection connection = Jsoup.connect(url);
		connection.timeout(10000);
		Elements allElements = connection.get().body().getAllElements();
		String text;

		if (allElements.hasClass("apphub_StorePrice")) {
			Elements elements = connection.get().body().getElementsByClass("price");

			if (elements.isEmpty()) {
				logger.debug("Tag \"price\" not found for " + url + ". Trying \"discount_original_price\"...");
				elements = connection.get().body().getElementsByClass("discount_original_price");
			}

			text = elements.text();

			if (text.contains("\u20AC")) {
				text = text.split("\u20AC")[0].replace(",", ".");
				currency = "\u20AC";
			} else if(text.contains("\u0024")){
				text = text.split("\\u0024")[1];
				currency = "\u0024";
			}
		} else {
			return 0.0;
		}

		return Double.parseDouble(text);
	}

	@Override
	public List<Game> retrieveGameListForAccount(String account) {
		List<Game> gameList = new ArrayList<Game>();

		try {
			createPriceMapFromDatabase();

			DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
			Document document;
			
			if(mainFrame.hasNoAlias()){
				document = documentBuilder.parse("http://steamcommunity.com/profiles/" + account + "/games?tab=all&xml=1");
			} else{
				document = documentBuilder.parse("http://steamcommunity.com/id/" + account + "/games?tab=all&xml=1");
			}
			
			gameList = createGameListFromXML(document, account);
		} catch (Exception e) {
			logger.error("Error occured while parsing game list.", e);
		}

		return gameList;
	}

	private void createPriceMapFromDatabase() throws SQLException {
		Statement statement = connection.createStatement();
		ResultSet currentDatabaseEntries = statement.executeQuery("SELECT * FROM games");

		if (!currentDatabaseEntries.next()) {
			logger.debug("There are no entries in the database");
			databaseEmpty = true;
		} else {
			currentDatabaseEntries.beforeFirst();
			gamePricesFromDatabse = new HashMap<Integer, Double>();
			while (currentDatabaseEntries.next()) {
				int appId = currentDatabaseEntries.getInt("id");
				double appPrice = currentDatabaseEntries.getDouble("regular_price");
				gamePricesFromDatabse.put(appId, appPrice);
			}
			databaseEmpty = false;
		}
	}

	private List<Game> createGameListFromXML(Document document, String account) {
		List<Game> gameList = new ArrayList<Game>();
		NodeList games = document.getElementsByTagName("game");

		for (int i = 0; i < games.getLength(); i++) {
			NodeList childNodes = games.item(i).getChildNodes();

			int id = Integer.parseInt(childNodes.item(1).getTextContent());
			String name = childNodes.item(3).getTextContent();
			String logo = childNodes.item(5).getTextContent();
			String storeLink = childNodes.item(7).getTextContent();
			double hoursOnRecord = 0;

			if (null != childNodes.item(9) && !childNodes.item(9).getTextContent().startsWith("http")) {
				hoursOnRecord = Double.parseDouble(childNodes.item(9).getTextContent());
			}

			Game game = new Game(id, name, logo, storeLink, hoursOnRecord);
			if (!databaseEmpty) {
				if (gamePricesFromDatabse.containsKey(id)) {
					game.setPrice(gamePricesFromDatabse.get(id));
					game.setHourValue(HelperUtil.calculateHourValueForGame(gamePricesFromDatabse.get(id), hoursOnRecord));
				}
			}

			gameList.add(game);
			logger.debug(name + " added to gameList for account:" + account);
		}
		return gameList;
	}

	@Override
	public String[][] createGameListArrayForTable(List<Game> gameList) {
		String[][] result = new String[gameList.size() + 1][5];

		int counter = 0;
		for (Game game : gameList) {
			result[counter][0] = game.getName();
			result[counter][1] = game.getStoreLink();
			result[counter][2] = Double.toString(game.getHoursOnRecord());
			result[counter][3] = formatDoubleToCurrency(game.getPrice());
			result[counter][4] = formatDoubleToCurrency(game.getHourValue());
			counter++;
		}

		double totalHours = HelperUtil.sumUpHoursFromGameList(gameList);
		double totalPrice = HelperUtil.sumUpPricesFromGameList(gameList);

		result[counter][0] = HelperUtil.createBoldHtmlText("Total");
		result[counter][2] = HelperUtil.createBoldHtmlText(Double.toString(HelperUtil.round(totalHours, 2)));
		result[counter][3] = HelperUtil.createBoldHtmlText(formatDoubleToCurrency(totalPrice));
		result[counter][4] = HelperUtil.createBoldHtmlText(formatDoubleToCurrency(HelperUtil.calculateHourValueForGame(
				totalPrice, totalHours)));

		return result;
	}

	@Override
	public List<Game> retrievePricedGameListWithProgressBar(List<Game> gameList, JProgressBar progressBar, JTable table)
			throws IOException {
		table.setValueAt(HelperUtil.createBoldHtmlText("Total"), gameList.size(), 0);

		int counter = 0;
		for (Game game : gameList) {
			double price = retrievePriceFromUrl(game.getStoreLink());
			game.setPrice(price);

			if (price == 0.0) {
				table.setValueAt("Free", counter, 3);
			} else {
				table.setValueAt(formatDoubleToCurrency(price), counter, 3);
			}

			double hourValue = HelperUtil.calculateHourValueForGame(game.getPrice(), game.getHoursOnRecord());

			if (hourValue == 0.0) {
				table.setValueAt("None", counter, 4);
			} else {
				String formattedValue = formatDoubleToCurrency(hourValue);
				table.setValueAt(formattedValue, counter, 4);
			}

			progressBar.setValue(counter + 1);

			logger.debug("Price set for game:" + game.getName() + " to " + price);

			writeGameToDatabase(game.getId(), game.getName(), game.getLogo(), game.getStoreLink(), game.getPrice(), currency);

			counter++;
		}

		double totalHours = HelperUtil.sumUpHoursFromGameList(gameList);
		double totalPrice = HelperUtil.sumUpPricesFromGameList(gameList);

		table.setValueAt(HelperUtil.createBoldHtmlText(Double.toString(HelperUtil.round(totalHours, 2))), counter, 2);
		String currencyTotalPrice = formatDoubleToCurrency(totalPrice);
		table.setValueAt(HelperUtil.createBoldHtmlText(currencyTotalPrice), counter, 3);

		double totalHourValue = totalPrice / totalHours;
		if (Double.isInfinite(totalHourValue)) { // games cost something, never played
			table.setValueAt(HelperUtil.createBoldHtmlText(currencyTotalPrice), counter, 4);
		} else if (totalPrice == 0) { // games cost nothing, value is infinite
			String currencyZero = formatDoubleToCurrency(0);
			table.setValueAt(HelperUtil.createBoldHtmlText(currencyZero), counter, 4);
		} else {
			String currencyTotalHourValue = formatDoubleToCurrency(totalHourValue);
			table.setValueAt(HelperUtil.createBoldHtmlText(currencyTotalHourValue), counter, 4);
		}

		return gameList;
	}

	private void writeGameToDatabase(int id, String name, String logoUrl, String storeUrl, double regularPrice, String currency) {
		name = name.replace("'", "\\'");

		//@formatter:off
		String sql = "INSERT INTO games VALUES (" + 
				id + ", '" + 
				name + "', '" + 
				logoUrl + "', '" + 
				storeUrl + "', " + 
				regularPrice + ", '" + 
				currency + "', " +
				"CURRENT_TIMESTAMP)";
		//@formatter:on

		try {
			Statement statement = connection.createStatement();
			ResultSet result = statement.executeQuery("SELECT id, regular_price FROM games WHERE id = " + id);
			result.next();

			if (result.getRow() == 0) {
				statement.execute(sql);
				logger.debug("Entry for " + name + " added to games.");
			} else {
				double appPrice = result.getDouble("regular_price");

				if (appPrice != regularPrice) {
					statement.execute("UPDATE games SET regular_price = " + regularPrice
							+ ", last_checked = CURRENT_TIMESTAMP WHERE id = " + id);
					logger.debug("Price for " + name + " was updated. (Old: " + appPrice + " New: " + regularPrice + ")");
				} else {
					logger.debug("Price has not changed since last check.");
				}
			}

		} catch (SQLException e) {
			logger.error("Error occured during database operation with id:" + id, e);
		}
	}

	public String formatDoubleToCurrency(double price) {
		return HelperUtil.round(price, 2) + " " + currency;
	}

	@Override
	public Map<String, String> getConfigsMap() {
		return configsMap;
	}

	@Override
	public Map<String, String[]> getCsvMap() {
		return csvMap;
	}

}
